using IoTCenter.Utilities.Extensions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace Ganweisoft.IoTCenter.Module.MediaPlatform.Utilities
{
    public class ApiHttpClient
    {
        public HttpClient Client { get; }
        public IHttpClientFactory HttpClientFactory { get; }

        private readonly ILogger<ApiHttpClient> _logger;

        protected static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(5);

        #region Constructs
        public ApiHttpClient(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory, string name = "")
            : this(httpClientFactory, loggerFactory, DefaultTimeout, name) { }

        public ApiHttpClient(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory, TimeSpan timeout, string name = "")
        {
            if (httpClientFactory == null) { throw new ArgumentNullException(nameof(httpClientFactory)); }
            if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); }

            _logger = loggerFactory.CreateLogger<ApiHttpClient>();

            if (name.IsEmpty())
            {
                Client = httpClientFactory.CreateClient();
            }
            else
            {
                Client = httpClientFactory.CreateClient(name);
            }
            Client.Timeout = timeout;
            Client.DefaultRequestHeaders.Accept.Clear();
            Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
        #endregion

        /// <summary> 
        /// Http Post
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        public Task<HttpResponseMessage> PostAsJsonAsyncWithLog(string requestUri, object data = null)
        {
            return SendAsJsonAsyncWithLog(HttpMethod.Post, requestUri, data);
        }

        /// <summary>
        /// Http Put
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        public Task<HttpResponseMessage> PutAsJsonAsyncWithLog(string requestUri, object data = null)
        {
            return SendAsJsonAsyncWithLog(HttpMethod.Put, requestUri, data);
        }

        /// <summary>
        /// Http Delete
        /// </summary>
        /// <param name="requestUri"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        public Task<HttpResponseMessage> DeleteAsJsonAsyncWithLog(string requestUri)
        {
            return SendAsJsonAsyncWithLog(HttpMethod.Delete, requestUri, null);
        }

        /// <summary>
        /// Http Get
        /// </summary>
        /// <param name="relativeUri"></param>
        /// <param name="queryObject"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        public Task<HttpResponseMessage> GetAsQueryStringAsyncWithLog(string relativeUri, object queryObject)
        {
            if (queryObject != null)
            {
                var queryString = queryObject.ToQueryString();
                if (queryString.HasValue())
                {
                    relativeUri = $"{relativeUri}?{queryString}";
                }
            }
            return GetAsQueryStringAsyncWithLog(relativeUri);
        }

        /// <summary>
        /// Http Get
        /// </summary>
        /// <param name="relativeUri"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        public Task<HttpResponseMessage> GetAsQueryStringAsyncWithLog(string relativeUri, IDictionary<string, string> parameters)
        {
            if (parameters != null)
            {
                relativeUri = relativeUri + '?' + string.Join("&",
                    parameters.Select(d => d.Key + "=" + WebUtility.UrlEncode(d.Value)));
            }
            return GetAsQueryStringAsyncWithLog(relativeUri);
        }

        /// <summary>
        /// Http Get
        /// </summary>
        /// <param name="requestUri"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        public async Task<HttpResponseMessage> GetAsQueryStringAsyncWithLog(string requestUri)
        {
            HttpResponseMessage response = null;
            DateTime beginRequestTime = DateTime.Now;
            try
            {
                response = await Client.GetAsync(requestUri);
            }
            catch (Exception exp)
            {
                _logger.LogError(exp, $"Request {requestUri} error.");
                throw new ApiException(exp.Message, ApiErrorCodes.InternalServerError, exp);
            }
            finally
            {
                LogApiRequest(beginRequestTime, response);
            }
            if (response != null && !response.IsSuccessStatusCode)
            {
                _logger.LogError("{method} - {url} - {code}",
                    HttpMethod.Get, requestUri, response.StatusCode);
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="requestUri"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        /// <exception cref="ApiException"></exception>
        private async Task<HttpResponseMessage> SendAsJsonAsyncWithLog(HttpMethod httpMethod, string requestUri, object data)
        {
            HttpContent content = null;

            if (data != null)
            {
                content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
            }

            HttpResponseMessage response = null;
            DateTime beginRequestTime = DateTime.Now;
            try
            {
                switch (httpMethod.Method)
                {
                    case "POST":
                        response = await Client.PostAsync(requestUri, content);
                        break;
                    case "PUT":
                        response = await Client.PutAsync(requestUri, content);
                        break;
                    case "DELETE":
                        response = await Client.DeleteAsync(requestUri);
                        break;
                    case "GET":
                        response = await Client.GetAsync(requestUri);
                        break;
                    default:
                        throw new ApiException($"Http method {httpMethod.Method} not supported", "NotSupported");
                }
            }
            catch (Exception exp)
            {
                _logger.LogError(exp, $"Request {requestUri} error.");
                throw new ApiException(exp.Message, ApiErrorCodes.InternalServerError, exp);
            }
            finally
            {
                LogApiRequest(beginRequestTime, response);
            }

            if (response != null && !response.IsSuccessStatusCode)
            {
                _logger.LogError("{method} - {url} - {code}",
                    httpMethod.Method, requestUri, response.StatusCode);
            }

            return response;
        }

        protected virtual void LogApiRequest(DateTime beginRequestTime, HttpResponseMessage response)
        {
            if (response != null)
            {
                _logger.LogTrace("{0} {1},Response:{2}"
                    , response.RequestMessage.Method
                    , response.RequestMessage.RequestUri
                    , response.StatusCode);
            }
        }

        protected async Task<ApiResult<T>> ParseHttpResponseMessage<T>(HttpResponseMessage responseMessage, string methodName, bool logResponse = false) where T : class
        {
            var apiResult = new ApiResult<T>();
            if (responseMessage.IsSuccessStatusCode)
            {
                apiResult.IsSuccessed = true;
                var responseContent = await responseMessage.Content.ReadAsStringAsync();
                if (string.IsNullOrWhiteSpace(responseContent))
                {
                    apiResult.Code = "api_response_empty";
                    return apiResult;
                }

                try
                {
                    var rspObj = JsonConvert.DeserializeObject<T>(responseContent);
                    if (rspObj == null)
                    {
                        apiResult.IsSuccessed = false;
                        apiResult.Code = "api_response_null";
                        apiResult.Message = $"接口未按照协议返回数据(API={methodName})";
                        return apiResult;
                    }

                    apiResult.Data = rspObj;

                    return apiResult;
                }
                catch (Exception exp)
                {
                    apiResult.Code = "api_response_parse_error";
                    apiResult.Message = $"接口响应数据解析失败(API={methodName})";
                    _logger.LogError(exp, $"API-PARSE-ERROR.{methodName}:{exp.Message},responseContent:{responseContent}");
#if DEBUG
                    apiResult.StackTrace = exp.StackTrace;
#endif
                }
            }
            else
            {
                ParseErrorResponse(responseMessage, apiResult);
            }
            return apiResult;
        }

        protected static void ParseErrorResponse(HttpResponseMessage response, ApiResult apiResult)
        {
            if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
            {
                apiResult.Code = "BadRequest";
                apiResult.Message = "HTTP 400。访问接口时，服务器未能识别请求。";
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
            {
                apiResult.Code = "InternalServerError";
                apiResult.Message = string.Format("HTTP 500。访问接口时，服务器返回异常。{0}",
                    response.ReasonPhrase);
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
            {
                apiResult.Code = "MethodNotAllowed";
                apiResult.Message = string.Format("HTTP 405。请求的资源\"{0}\"上不允许请求方法（{1}）。",
                    response.RequestMessage?.RequestUri, response.RequestMessage?.Method.ToString());
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                apiResult.Code = "NotFound";
                apiResult.Message = string.Format("HTTP 404。接口地址\"{0}\"不存在。请核对该接口地址是否真实存在或配置文件是否配置正确。",
                    response.RequestMessage?.RequestUri);
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.BadGateway)
            {
                apiResult.Code = "BadGateway";
                apiResult.Message = "HTTP 502。中间代理服务器从另一代理或原始服务器接收到错误响应。";
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
            {
                apiResult.Code = "ServiceUnavailable";
                apiResult.Message = "HTTP 503。接口服务器暂时不可用。";
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.GatewayTimeout)
            {
                apiResult.Code = "GatewayTimeout";
                apiResult.Message = "HTTP 504。访问接口时，服务器响应超时。";
            }
            else
            {
                apiResult.Code = "ApiResponseReadError";
                apiResult.Message = string.Format("{0}。{1}", response.StatusCode.ToString(), response.ReasonPhrase);
            }
        }
    }
}
